Passed
Branch v8.x (6e4ba7)
by Rafael S.
02:10
created

index.js ➔ assure16Bit_   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 6
rs 10
1
/*
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFile class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import bitDepthLib from './vendor/bitdepth.js';
33
import * as imaadpcm from './vendor/imaadpcm.js';
34
import * as alawmulaw from './vendor/alawmulaw.js';
35
import {encode, decode} from './vendor/base64-arraybuffer-es6.js';
36
import {unpackArray, packArrayTo, unpackArrayTo} from './vendor/byte-data.js';
37
import {wavHeader, validateHeader_} from './lib/wavheader.js';
38
import {riffChunks, findChunk_} from './vendor/riff-chunks.js';
39
import BufferIO from './lib/bufferio.js';
40
import writeWavBuffer from './lib/wav-buffer-writer.js';
41
import readWavBuffer from './lib/wav-buffer-reader.js';
42
import WavBuffer from './lib/wav-buffer.js';
43
44
/**
45
 * Class representing a wav file.
46
 * @extends WavBuffer
47
 * @ignore
48
 */
49
export default class WaveFile extends WavBuffer {
50
51
  /**
52
   * @param {?Uint8Array} bytes A wave file buffer.
53
   * @throws {Error} If no 'RIFF' chunk is found.
54
   * @throws {Error} If no 'fmt ' chunk is found.
55
   * @throws {Error} If no 'data' chunk is found.
56
   */
57
  constructor(bytes=null) {
58
    super();
59
    /**
60
     * The bit depth code according to the samples.
61
     * @type {string}
62
     */
63
    this.bitDepth = '0';
64
    /**
65
     * @type {!Object}
66
     * @private
67
     */
68
    this.dataType = {};
69
    this.io = new BufferIO();
70
    // Load a file from the buffer if one was passed
71
    // when creating the object
72
    if (bytes) {
73
      this.fromBuffer(bytes);
74
    }
75
  }
76
77
  /**
78
   * Set up the WaveFile object based on the arguments passed.
79
   * @param {number} numChannels The number of channels
80
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
81
   * @param {number} sampleRate The sample rate.
82
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
83
   * @param {string} bitDepthCode The audio bit depth code.
84
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
85
   *    or any value between '8' and '32' (like '12').
86
   * @param {!Array<number>|!Array<!Array<number>>|!ArrayBufferView} samples
87
   *    The samples. Must be in the correct range according to the bit depth.
88
   * @param {?Object} options Optional. Used to force the container
89
   *    as RIFX with {'container': 'RIFX'}
90
   * @throws {Error} If any argument does not meet the criteria.
91
   */
92
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options={}) {
93
    if (!options.container) {
94
      options.container = 'RIFF';
95
    }
96
    this.container = options.container;
97
    this.bitDepth = bitDepthCode;
98
    samples = this.interleave_(samples);
99
    this.updateDataType_();
100
    /** @type {number} */
101
    let numBytes = this.dataType.bits / 8;
102
    this.data.samples = new Uint8Array(samples.length * numBytes);
103
    packArrayTo(samples, this.dataType, this.data.samples);
104
    /** @type {!Object} */
105
    let header = wavHeader(
106
      bitDepthCode, numChannels, sampleRate,
107
      numBytes, this.data.samples.length, options);
108
    this.clearHeader_();
109
    this.chunkSize = header.chunkSize;
110
    this.format = header.format;
111
    this.fmt = header.fmt;
112
    if (header.fact) {
113
      this.fact = header.fact;
114
    }
115
    this.data.chunkId = 'data';
116
    this.data.chunkSize = this.data.samples.length;
117
    validateHeader_(this);
118
  }
119
120
  /**
121
   * Set up the WaveFile object from a byte buffer.
122
   * @param {!Uint8Array} bytes The buffer.
123
   * @param {boolean=} samples True if the samples should be loaded.
124
   * @throws {Error} If container is not RIFF, RIFX or RF64.
125
   * @throws {Error} If no 'fmt ' chunk is found.
126
   * @throws {Error} If no 'data' chunk is found.
127
   */
128
  fromBuffer(bytes, samples=true) {
129
    this.clearHeader_();
130
    readWavBuffer(bytes, samples, this);
131
    this.bitDepthFromFmt_();
132
    this.updateDataType_();
133
  }
134
135
  /**
136
   * Return a byte buffer representig the WaveFile object as a .wav file.
137
   * The return value of this method can be written straight to disk.
138
   * @return {!Uint8Array} A .wav file.
139
   * @throws {Error} If any property of the object appears invalid.
140
   */
141
  toBuffer() {
142
    validateHeader_(this);
143
    return writeWavBuffer(this);
144
  }
145
146
  /**
147
   * Use a .wav file encoded as a base64 string to load the WaveFile object.
148
   * @param {string} base64String A .wav file as a base64 string.
149
   * @throws {Error} If any property of the object appears invalid.
150
   */
151
  fromBase64(base64String) {
152
    this.fromBuffer(new Uint8Array(decode(base64String)));
153
  }
154
155
  /**
156
   * Return a base64 string representig the WaveFile object as a .wav file.
157
   * @return {string} A .wav file as a base64 string.
158
   * @throws {Error} If any property of the object appears invalid.
159
   */
160
  toBase64() {
161
    /** @type {!Uint8Array} */
162
    let buffer = this.toBuffer();
163
    return encode(buffer, 0, buffer.length);
164
  }
165
166
  /**
167
   * Return a DataURI string representig the WaveFile object as a .wav file.
168
   * The return of this method can be used to load the audio in browsers.
169
   * @return {string} A .wav file as a DataURI.
170
   * @throws {Error} If any property of the object appears invalid.
171
   */
172
  toDataURI() {
173
    return 'data:audio/wav;base64,' + this.toBase64();
174
  }
175
176
  /**
177
   * Use a .wav file encoded as a DataURI to load the WaveFile object.
178
   * @param {string} dataURI A .wav file as DataURI.
179
   * @throws {Error} If any property of the object appears invalid.
180
   */
181
  fromDataURI(dataURI) {
182
    this.fromBase64(dataURI.replace('data:audio/wav;base64,', ''));
183
  }
184
185
  /**
186
   * Force a file as RIFF.
187
   */
188
  toRIFF() {
189
    this.fromScratch(
190
      this.fmt.numChannels,
191
      this.fmt.sampleRate,
192
      this.bitDepth,
193
      unpackArray(this.data.samples, this.dataType));
194
  }
195
196
  /**
197
   * Force a file as RIFX.
198
   */
199
  toRIFX() {
200
    this.fromScratch(
201
      this.fmt.numChannels,
202
      this.fmt.sampleRate,
203
      this.bitDepth,
204
      unpackArray(this.data.samples, this.dataType),
205
      {container: 'RIFX'});
206
  }
207
208
  /**
209
   * Change the bit depth of the samples.
210
   * @param {string} newBitDepth The new bit depth of the samples.
211
   *    One of '8' ... '32' (integers), '32f' or '64' (floats)
212
   * @param {boolean} changeResolution A boolean indicating if the
213
   *    resolution of samples should be actually changed or not.
214
   * @throws {Error} If the bit depth is not valid.
215
   */
216
  toBitDepth(newBitDepth, changeResolution=true) {
217
    /** @type {string} */
218
    let toBitDepth = newBitDepth;
219
    /** @type {string} */
220
    let thisBitDepth = this.bitDepth;
221
    if (!changeResolution) {
222
      if (newBitDepth != '32f') {
223
        toBitDepth = this.dataType.bits.toString();
224
      }
225
      thisBitDepth = this.dataType.bits;
226
    }
227
    this.assureUncompressed_();
228
    /** @type {number} */
229
    let sampleCount = this.data.samples.length / (this.dataType.bits / 8);
230
    /** @type {!Float64Array} */
231
    let typedSamplesInput = new Float64Array(sampleCount + 1);
232
    /** @type {!Float64Array} */
233
    let typedSamplesOutput = new Float64Array(sampleCount + 1);
234
    unpackArrayTo(this.data.samples, this.dataType, typedSamplesInput);
235
    bitDepthLib(
236
      typedSamplesInput, thisBitDepth, toBitDepth, typedSamplesOutput);
237
    this.fromScratch(
238
      this.fmt.numChannels,
239
      this.fmt.sampleRate,
240
      newBitDepth,
241
      typedSamplesOutput,
242
      {container: this.correctContainer_()});
243
  }
244
245
  /**
246
   * Encode a 16-bit wave file as 4-bit IMA ADPCM.
247
   * @throws {Error} If sample rate is not 8000.
248
   * @throws {Error} If number of channels is not 1.
249
   */
250
  toIMAADPCM() {
251
    if (this.fmt.sampleRate !== 8000) {
252
      throw new Error(
253
        'Only 8000 Hz files can be compressed as IMA-ADPCM.');
254
    } else if (this.fmt.numChannels !== 1) {
255
      throw new Error(
256
        'Only mono files can be compressed as IMA-ADPCM.');
257
    } else {
258
      this.assure16Bit_();
259
      let output = new Int16Array(this.data.samples.length / 2);
260
      unpackArrayTo(this.data.samples, this.dataType, output);
261
      this.fromScratch(
262
        this.fmt.numChannels,
263
        this.fmt.sampleRate,
264
        '4',
265
        imaadpcm.encode(output),
266
        {container: this.correctContainer_()});
267
    }
268
  }
269
270
  /**
271
   * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
272
   * @param {string} bitDepthCode The new bit depth of the samples.
273
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
274
   *    Optional. Default is 16.
275
   */
276
  fromIMAADPCM(bitDepthCode='16') {
277
    this.fromScratch(
278
      this.fmt.numChannels,
279
      this.fmt.sampleRate,
280
      '16',
281
      imaadpcm.decode(this.data.samples, this.fmt.blockAlign),
282
      {container: this.correctContainer_()});
283
    if (bitDepthCode != '16') {
284
      this.toBitDepth(bitDepthCode);
285
    }
286
  }
287
288
  /**
289
   * Encode a 16-bit wave file as 8-bit A-Law.
290
   */
291
  toALaw() {
292
    this.assure16Bit_();
293
    let output = new Int16Array(this.data.samples.length / 2);
294
    unpackArrayTo(this.data.samples, this.dataType, output);
295
    this.fromScratch(
296
      this.fmt.numChannels,
297
      this.fmt.sampleRate,
298
      '8a',
299
      alawmulaw.alaw.encode(output),
300
      {container: this.correctContainer_()});
301
  }
302
303
  /**
304
   * Decode a 8-bit A-Law wave file into a 16-bit wave file.
305
   * @param {string} bitDepthCode The new bit depth of the samples.
306
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
307
   *    Optional. Default is 16.
308
   */
309
  fromALaw(bitDepthCode='16') {
310
    this.fromScratch(
311
      this.fmt.numChannels,
312
      this.fmt.sampleRate,
313
      '16',
314
      alawmulaw.alaw.decode(this.data.samples),
315
      {container: this.correctContainer_()});
316
    if (bitDepthCode != '16') {
317
      this.toBitDepth(bitDepthCode);
318
    }
319
  }
320
321
  /**
322
   * Encode 16-bit wave file as 8-bit mu-Law.
323
   */
324
  toMuLaw() {
325
    this.assure16Bit_();
326
    let output = new Int16Array(this.data.samples.length / 2);
327
    unpackArrayTo(this.data.samples, this.dataType, output);
328
    this.fromScratch(
329
      this.fmt.numChannels,
330
      this.fmt.sampleRate,
331
      '8m',
332
      alawmulaw.mulaw.encode(output),
333
      {container: this.correctContainer_()});
334
  }
335
336
  /**
337
   * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
338
   * @param {string} bitDepthCode The new bit depth of the samples.
339
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
340
   *    Optional. Default is 16.
341
   */
342
  fromMuLaw(bitDepthCode='16') {
343
    this.fromScratch(
344
      this.fmt.numChannels,
345
      this.fmt.sampleRate,
346
      '16',
347
      alawmulaw.mulaw.decode(this.data.samples),
348
      {container: this.correctContainer_()});
349
    if (bitDepthCode != '16') {
350
      this.toBitDepth(bitDepthCode);
351
    }
352
  }
353
354
  /**
355
   * Write a RIFF tag in the INFO chunk. If the tag do not exist,
356
   * then it is created. It if exists, it is overwritten.
357
   * @param {string} tag The tag name.
358
   * @param {string} value The tag value.
359
   * @throws {Error} If the tag name is not valid.
360
   */
361
  setTag(tag, value) {
362
    tag = this.fixTagName_(tag);
363
    /** @type {!Object} */
364
    let index = this.getTagIndex_(tag);
365
    if (index.TAG !== null) {
366
      this.LIST[index.LIST].subChunks[index.TAG].chunkSize =
367
        value.length + 1;
368
      this.LIST[index.LIST].subChunks[index.TAG].value = value;
369
    } else if (index.LIST !== null) {
370
      this.LIST[index.LIST].subChunks.push({
371
        chunkId: tag,
372
        chunkSize: value.length + 1,
373
        value: value});
374
    } else {
375
      this.LIST.push({
376
        chunkId: 'LIST',
377
        chunkSize: 8 + value.length + 1,
378
        format: 'INFO',
379
        subChunks: []});
380
      this.LIST[this.LIST.length - 1].subChunks.push({
381
        chunkId: tag,
382
        chunkSize: value.length + 1,
383
        value: value});
384
    }
385
  }
386
387
  /**
388
   * Return the value of a RIFF tag in the INFO chunk.
389
   * @param {string} tag The tag name.
390
   * @return {?string} The value if the tag is found, null otherwise.
391
   */
392
  getTag(tag) {
393
    /** @type {!Object} */
394
    let index = this.getTagIndex_(tag);
395
    if (index.TAG !== null) {
396
      return this.LIST[index.LIST].subChunks[index.TAG].value;
397
    }
398
    return null;
399
  }
400
401
  /**
402
   * Remove a RIFF tag in the INFO chunk.
403
   * @param {string} tag The tag name.
404
   * @return {boolean} True if a tag was deleted.
405
   */
406
  deleteTag(tag) {
407
    /** @type {!Object} */
408
    let index = this.getTagIndex_(tag);
409
    if (index.TAG !== null) {
410
      this.LIST[index.LIST].subChunks.splice(index.TAG, 1);
411
      return true;
412
    }
413
    return false;
414
  }
415
416
  /**
417
   * Create a cue point in the wave file.
418
   * @param {number} position The cue point position in milliseconds.
419
   * @param {string} labl The LIST adtl labl text of the marker. Optional.
420
   */
421
  setCuePoint(position, labl='') {
422
    this.cue.chunkId = 'cue ';
423
    position = (position * this.fmt.sampleRate) / 1000;
424
    /** @type {!Array<!Object>} */
425
    let existingPoints = this.getCuePoints_();
426
    this.clearLISTadtl_();
427
    /** @type {number} */
428
    let len = this.cue.points.length;
429
    this.cue.points = [];
430
    /** @type {boolean} */
431
    let hasSet = false;
432
    if (len === 0) {
433
      this.setCuePoint_(position, 1, labl);
434
    } else {
435
      for (let i=0; i<len; i++) {
436
        if (existingPoints[i].dwPosition > position && !hasSet) {
437
          this.setCuePoint_(position, i + 1, labl);
438
          this.setCuePoint_(
439
            existingPoints[i].dwPosition,
440
            i + 2,
441
            existingPoints[i].label);
442
          hasSet = true;
443
        } else {
444
          this.setCuePoint_(
445
            existingPoints[i].dwPosition,
446
            i + 1,
447
            existingPoints[i].label);
448
        }
449
      }
450
      if (!hasSet) {
451
        this.setCuePoint_(position, this.cue.points.length + 1, labl);
452
      }
453
    }
454
    this.cue.dwCuePoints = this.cue.points.length;
455
  }
456
457
  /**
458
   * Remove a cue point from a wave file.
459
   * @param {number} index the index of the point. First is 1,
460
   *    second is 2, and so on.
461
   */
462
  deleteCuePoint(index) {
463
    this.cue.chunkId = 'cue ';
464
    /** @type {!Array<!Object>} */
465
    let existingPoints = this.getCuePoints_();
466
    this.clearLISTadtl_();
467
    /** @type {number} */
468
    let len = this.cue.points.length;
469
    this.cue.points = [];
470
    for (let i=0; i<len; i++) {
471
      if (i + 1 !== index) {
472
        this.setCuePoint_(
473
          existingPoints[i].dwPosition,
474
          i + 1,
475
          existingPoints[i].label);
476
      }
477
    }
478
    this.cue.dwCuePoints = this.cue.points.length;
479
    if (this.cue.dwCuePoints) {
480
      this.cue.chunkId = 'cue ';
481
    } else {
482
      this.cue.chunkId = '';
483
      this.clearLISTadtl_();
484
    }
485
  }
486
487
  /**
488
   * Update the label of a cue point.
489
   * @param {number} pointIndex The ID of the cue point.
490
   * @param {string} label The new text for the label.
491
   */
492
  updateLabel(pointIndex, label) {
493
    /** @type {?number} */
494
    let adtlIndex = this.getAdtlChunk_();
495
    if (adtlIndex !== null) {
496
      for (let i=0; i<this.LIST[adtlIndex].subChunks.length; i++) {
497
        if (this.LIST[adtlIndex].subChunks[i].dwName ==
498
            pointIndex) {
499
          this.LIST[adtlIndex].subChunks[i].value = label;
500
        }
501
      }
502
    }
503
  }
504
505
  /**
506
   * Set the string code of the bit depth based on the 'fmt ' chunk.
507
   * @private
508
   */
509
  bitDepthFromFmt_() {
510
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
511
      this.bitDepth = '32f';
512
    } else if (this.fmt.audioFormat === 6) {
513
      this.bitDepth = '8a';
514
    } else if (this.fmt.audioFormat === 7) {
515
      this.bitDepth = '8m';
516
    } else {
517
      this.bitDepth = this.fmt.bitsPerSample.toString();
518
    }
519
  }
520
  
521
  /**
522
   * Push a new cue point in this.cue.points.
523
   * @param {number} position The position in milliseconds.
524
   * @param {number} dwName the dwName of the cue point
525
   * @private
526
   */
527
  setCuePoint_(position, dwName, label) {
528
    this.cue.points.push({
529
      dwName: dwName,
530
      dwPosition: position,
531
      fccChunk: 'data',
532
      dwChunkStart: 0,
533
      dwBlockStart: 0,
534
      dwSampleOffset: position,
535
    });
536
    this.setLabl_(dwName, label);
537
  }
538
539
  /**
540
   * Return an array with the position of all cue points in the file.
541
   * @return {!Array<!Object>}
542
   * @private
543
   */
544
  getCuePoints_() {
545
    /** @type {!Array<!Object>} */
546
    let points = [];
547
    for (let i=0; i<this.cue.points.length; i++) {
548
      points.push({
549
        dwPosition: this.cue.points[i].dwPosition,
550
        label: this.getLabelForCuePoint_(
551
          this.cue.points[i].dwName)});
552
    }
553
    return points;
554
  }
555
556
  /**
557
   * Return the label of a cue point.
558
   * @param {number} pointDwName The ID of the cue point.
559
   * @return {string}
560
   * @private
561
   */
562
  getLabelForCuePoint_(pointDwName) {
563
    /** @type {?number} */
564
    let adtlIndex = this.getAdtlChunk_();
565
    if (adtlIndex !== null) {
566
      for (let i=0; i<this.LIST[adtlIndex].subChunks.length; i++) {
567
        if (this.LIST[adtlIndex].subChunks[i].dwName ==
568
            pointDwName) {
569
          return this.LIST[adtlIndex].subChunks[i].value;
570
        }
571
      }
572
    }
573
    return '';
574
  }
575
576
  /**
577
   * Clear any LIST chunk labeled as 'adtl'.
578
   * @private
579
   */
580
  clearLISTadtl_() {
581
    for (let i=0; i<this.LIST.length; i++) {
582
      if (this.LIST[i].format == 'adtl') {
583
        this.LIST.splice(i);
584
      }
585
    }
586
  }
587
588
  /**
589
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
590
   * @param {number} dwName The ID of the cue point.
591
   * @param {string} label The label for the cue point.
592
   * @private
593
   */
594
  setLabl_(dwName, label) {
595
    /** @type {?number} */
596
    let adtlIndex = this.getAdtlChunk_();
597
    if (adtlIndex === null) {
598
      this.LIST.push({
599
        chunkId: 'LIST',
600
        chunkSize: 4,
601
        format: 'adtl',
602
        subChunks: []});
603
      adtlIndex = this.LIST.length - 1;
604
    }
605
    this.setLabelText_(adtlIndex === null ? 0 : adtlIndex, dwName, label);
606
  }
607
608
  /**
609
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
610
   * @param {number} adtlIndex The index of the 'adtl' LIST in this.LIST.
611
   * @param {number} dwName The ID of the cue point.
612
   * @param {string} label The label for the cue point.
613
   * @private
614
   */
615
  setLabelText_(adtlIndex, dwName, label) {
616
    this.LIST[adtlIndex].subChunks.push({
617
      chunkId: 'labl',
618
      chunkSize: label.length,
619
      dwName: dwName,
620
      value: label
621
    });
622
    this.LIST[adtlIndex].chunkSize += label.length + 4 + 4 + 4 + 1;
623
  }
624
625
  /**
626
   * Return the index of the 'adtl' LIST in this.LIST.
627
   * @return {?number}
628
   * @private
629
   */
630
  getAdtlChunk_() {
631
    for (let i=0; i<this.LIST.length; i++) {
632
      if (this.LIST[i].format == 'adtl') {
633
        return i;
634
      }
635
    }
636
    return null;
637
  }
638
639
  /**
640
   * Return the index of a tag in a FILE chunk.
641
   * @param {string} tag The tag name.
642
   * @return {!Object<string, ?number>}
643
   *    Object.LIST is the INFO index in LIST
644
   *    Object.TAG is the tag index in the INFO
645
   * @private
646
   */
647
  getTagIndex_(tag) {
648
    /** @type {!Object<string, ?number>} */
649
    let index = {LIST: null, TAG: null};
650
    for (let i=0; i<this.LIST.length; i++) {
651
      if (this.LIST[i].format == 'INFO') {
652
        index.LIST = i;
653
        for (let j=0; j<this.LIST[i].subChunks.length; j++) {
654
          if (this.LIST[i].subChunks[j].chunkId == tag) {
655
            index.TAG = j;
656
            break;
657
          }
658
        }
659
        break;
660
      }
661
    }
662
    return index;
663
  }
664
665
  /**
666
   * Fix a RIFF tag format if possible, throw an error otherwise.
667
   * @param {string} tag The tag name.
668
   * @return {string} The tag name in proper fourCC format.
669
   * @private
670
   */
671
  fixTagName_(tag) {
672
    if (tag.constructor !== String) {
673
      throw new Error('Invalid tag name.');
674
    } else if (tag.length < 4) {
675
      for (let i=0; i<4-tag.length; i++) {
676
        tag += ' ';
677
      }
678
    }
679
    return tag;
680
  }
681
682
  /**
683
   * Reset attributes that should emptied when a file is
684
   * created with the fromScratch() or fromBuffer() methods.
685
   * @private
686
   */
687
  clearHeader_() {
688
    this.fmt.cbSize = 0;
689
    this.fmt.validBitsPerSample = 0;
690
    this.fact.chunkId = '';
691
    this.ds64.chunkId = '';
692
  }
693
694
  /**
695
   * Make the file 16-bit if it is not.
696
   * @private
697
   */
698
  assure16Bit_() {
699
    this.assureUncompressed_();
700
    if (this.bitDepth != '16') {
701
      this.toBitDepth('16');
702
    }
703
  }
704
705
  /**
706
   * Uncompress the samples in case of a compressed file.
707
   * @private
708
   */
709
  assureUncompressed_() {
710
    if (this.bitDepth == '8a') {
711
      this.fromALaw();
712
    } else if (this.bitDepth == '8m') {
713
      this.fromMuLaw();
714
    } else if (this.bitDepth == '4') {
715
      this.fromIMAADPCM();
716
    }
717
  }
718
719
  /**
720
   * Set up the WaveFile object from a byte buffer.
721
   * @param {!Array<number>|!Array<!Array<number>>|!ArrayBufferView} samples The samples.
722
   * @private
723
   */
724
  interleave_(samples) {
725
    if (samples.length > 0) {
726
      if (samples[0].constructor === Array) {
727
        /** @type {!Array<number>} */
728
        let finalSamples = [];
729
        for (let i=0; i < samples[0].length; i++) {
730
          for (let j=0; j < samples.length; j++) {
731
            finalSamples.push(samples[j][i]);
732
          }
733
        }
734
        samples = finalSamples;
735
      }
736
    }
737
    return samples;
738
  }
739
740
  /**
741
   * Update the type definition used to read and write the samples.
742
   * @private
743
   */
744
  updateDataType_() {
745
    /** @type {!Object} */
746
    this.dataType = {
747
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
748
      float: this.bitDepth == '32f' || this.bitDepth == '64',
749
      signed: this.bitDepth != '8',
750
      be: this.container == 'RIFX'
751
    };
752
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
753
      this.dataType.bits = 8;
754
      this.dataType.signed = false;
755
    }
756
  }
757
758
  /**
759
   * Return 'RIFF' if the container is 'RF64', the current container name
760
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
761
   * @return {string}
762
   * @private
763
   */
764
  correctContainer_() {
765
    return this.container == 'RF64' ? 'RIFF' : this.container;
766
  }
767
}
768